#pragma once

#include <iostream>
#include <string>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "../commen/log.hpp"
#include "../commen/util.hpp"

namespace ns_runner
{
    class runner
    {
    public:
        runner() {}
        ~runner() {}

        // 提供设置进程占用资源大小的接口
        static void set_proc_limit(int cpu_limit, int mem_limit)
        {
            // 设置CPU执行时长
            struct rlimit cpu_rlimit;
            cpu_rlimit.rlim_max = RLIM_INFINITY;
            cpu_rlimit.rlim_cur = cpu_limit;
            setrlimit(RLIMIT_CPU, &cpu_rlimit);

            // 设置进程内存使用大小
            struct rlimit mem_rlimit;
            mem_rlimit.rlim_max = RLIM_INFINITY;
            mem_rlimit.rlim_cur = mem_limit * 1024; // 转换为Kb
            setrlimit(RLIMIT_AS, &mem_rlimit);
        }

        /**************************************************
         *run函数指定文件名即可,不需要带路径,也不需要带后缀
         *
         *返回值 > 0:子进程异常退出,返回子进程退出时收到的信号
         *返回值 == 0:子进程正常退出,没有收到信号
         *返回值 < 0:父进程内部出现了问题
         *
         *cpu_limit:子进程运行时,CPU可以执行的最大时间
         *mem_limit:子进程运行时,可以使用的最大内存(Kb)
         *****************************************************/

        static int run(const std::string &file_name,
                       int cpu_limit,
                       int mem_limit)
        {
            // 程序运行的时候有三种情况
            //  1.代码跑完,结果正确
            //  2.代码跑完,结果不正确
            //  3.代码没跑完,出现异常了
            //  对于run函数来说,需要考虑1,2的情况吗?
            //  那么当然不考虑了
            //  因为结果的正确与否是由我们的测试用例来进行判断的
            //  run只需要考虑代码是否能够正常的执行

            // 我们需要让run知道可执行程序在哪

            // 当一个程序在默认启动的时候
            // 标准输入:我们不进行处理(也就是说我们没有实现类似于在线输入测试用例的功能)
            // 标准输出:程序运行成功时,保存输出结果
            // 标准错误:保存运行时出错的信息
            std::string execute = ns_util::path_util::exe(file_name);
            std::string stdin = ns_util::path_util::stdin(file_name);
            std::string stdout = ns_util::path_util::stdout(file_name);
            std::string stderr = ns_util::path_util::stderr(file_name);

            umask(0);
            int stdin_fd = open(stdin.c_str(), O_CREAT | O_RDONLY, 0644);
            int stdout_fd = open(stdout.c_str(), O_CREAT | O_WRONLY, 0644);
            int stderr_fd = open(stderr.c_str(), O_CREAT | O_WRONLY, 0644);

            if (stdin_fd < 0 || stdout_fd < 0 || stderr_fd < 0)
            {
                ns_log::LOG(ERROR) << "运行程序时,标准文件打开失败!" << std::endl;

                return -1; // 代表打开文件失败
            }

            pid_t pid = fork();

            if (pid < 0)
            {
                ns_log::LOG(ERROR) << "运行程序时,创建子进程失败!" << std::endl;
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd);

                return -2; // 代表创建子进程失败
            }
            else if (pid == 0)
            {
                // 子进程
                dup2(stdin_fd, 0);

                dup2(stdout_fd, 1);

                dup2(stderr_fd, 2);

                set_proc_limit(cpu_limit, mem_limit);

                execl(execute.c_str(), execute.c_str(), nullptr);

                // 如果到了这里说明程序替换失败了
                ns_log::LOG(ERROR) << "子进程执行代码失败!" << std::endl;
                exit(1);
            }
            else
            {
                // 父进程
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd);

                int status = 0;
                waitpid(pid, &status, 0);
                // 程序运行异常,一定是因为收到了信号,status的低7位就是进程退出时的信号
                ns_log::LOG(INFO) << "运行完毕,退出信号为" << (status & 0x7F) << std::endl;

                return status & 0x7F;
            }
        }
    };
}